
/*
 * sndpipe: A UNIX pipes-based audio stream processor toolkit.
 * Copyright (C) 2011  Bradley Worley
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to:
 *
 *   Free Software Foundation, Inc.
 *   51 Franklin Street, Fifth Floor
 *   Boston, MA  02110-1301, USA.
 */

/* include the sndpipe header. */
#include "sndpipe.h"

/* define the operations we are capable of performing. */
#define SP_MATHOP_ADD 1
#define SP_MATHOP_SUB 2
#define SP_MATHOP_MUL 3
#define SP_MATHOP_DIV 4

/* define the operation format for parsing operation option strings. */
struct math_operation {
  char *str;
  int opid;
};

/* declare the array of accepted operation types. */
static struct math_operation ops[] = {
  { SP_FUNC_ADD, SP_MATHOP_ADD },
  { SP_FUNC_SUB, SP_MATHOP_SUB },
  { SP_FUNC_MUL, SP_MATHOP_MUL },
  { SP_FUNC_DIV, SP_MATHOP_DIV },
  { SP_UNKNOWN, SP_UNKNOWN }
};

/* math_get_op: parses a string for math operation type information.
 *
 * this function pulls its information from the function argument in the
 * options parsing array. values are compared to accepted values and, if
 * a match is found, the corresponding operation integer is returned.
 * otherwise, a zero value is returned.
 */
int math_get_op (void) {
  /* declare required variables. */
  char *opstr;
  int i;

  /* get the operation string from the parsed command-line arguments. */
  opstr = opts_get_string (OPTS_S_FUNCTION);

  /* ensure the op string is non-null. */
  if (!opstr)
    return SP_UNKNOWN;

  /* loop through the array of known operation strings. */
  for (i = 0; ops[i].str; i++) {
    /* see if the currently indexed operation string is a match. */
    if (strcmp (ops[i].str, opstr) == 0)
      return ops[i].opid;
  }

  /* return zero. we failed to find a match. */
  return SP_UNKNOWN;
}

/* func_math: initiates the ADD, SUB, MUL and DIV functionalities.
 *
 * this function handles all four of the basic arithmetic operations on
 * pairs of channels. it opens up the input and output pipes (both required),
 * determines which channels to operate on, adds the result to a new channel
 * at the end of the stream, and deletes the original channels if requested.
 */
void func_math (void) {
  /* declare required variables. */
  unsigned long c, i, frames;
  struct pipes *pi, *po;
  double **rbuf, **wbuf;
  SF_INFO sfi;

  /* declare required math variables. */
  unsigned long c1, c2;
  int op, rm, skip;

  /* read in the input channel numbers and the remove argument. */
  rm = opts_get_int (OPTS_S_REMOVE);
  c1 = opts_get_int (OPTS_S_CHAN_ONE);
  c2 = opts_get_int (OPTS_S_CHAN_TWO);

  /* ensure the channel numbers were supplied. */
  if (!c1 || !c2) {
    /* print a warning message and set default values. */
    warnf ("unknown input channel numbers. assuming first two");
    c1 = 1;
    c2 = 2;
  }

  /* read in the operation type. */
  op = math_get_op ();

  /* ensure the operation type was supplied. */
  if (!op) {
    /* print a warning message and use the default operation. */
    warnf ("unknown operation type. assuming addition");
    op = SP_MATHOP_ADD;
  }

  /* zero out the sndfile information structure. */
  memset (&sfi, 0, sizeof (sfi));

  /* attempt to open the input pipe. */
  pi = pipes_open_input ();

  /* ensure the input pipe was successfully opened. */
  if (!pi) {
    /* print a failure message and exit the application. */
    failf ("unable to open input pipe");
  }

  /* set the output pipe sample rate and format from the input pipe. */
  sfi.samplerate = pi->sfi.samplerate;
  sfi.format = pi->sfi.format;

  /* ensure we have an output format. */
  if (!sfi.format) {
    /* print a failure message and exit the application. */
    failf ("unable to retrieve pipes output format");
  }

  /* determine the number of output channels. */
  if (rm)
    sfi.channels = pi->sfi.channels - 1;
  else
    sfi.channels = pi->sfi.channels + 1;

  /* open the output pipes structure. */
  po = pipes_open_output (sfi);

  /* allocate the pipes input and output buffer memories. */
  frames = 0;
  rbuf = pipes_buf_alloc (pi, &frames);
  wbuf = pipes_buf_alloc (po, &frames);

  /* loop while we can write samples to the pipe. */
  do {
    /* attempt to read from the input pipe. */
    if (pipes_read (pi, &rbuf, frames) != SP_OK) {
      /* print a failure message and break the loop. */
      warnf ("unable to read from input audio pipe");
      break;
    }

    /* act based on whether we remove the inputs or not. */
    if (rm) {
      /* initialize the skip counter. */
      skip = 0;

      /* loop through the input channels. */
      for (c = 0; c < pi->sfi.channels; c++) {
        /* is this channel scheduled to be removed? */
        if (c == c1 - 1 || c == c2 - 1) {
          /* skip this channel. */
          skip--;
          continue;
        }

        /* loop through the non-skip input frames. */
        for (i = 0; i < frames; i++)
          wbuf[c + skip][i] = rbuf[c][i];
      }
    }
    else {
      /* loop through the input channels. */
      for (c = 0; c < pi->sfi.channels; c++) {
        /* and through the number of data frames, copying. */
        for (i = 0; i < frames; i++)
          wbuf[c][i] = rbuf[c][i];
      }
    }

    /* loop through the number of output result channel frames. */
    for (i = 0; i < frames; i++) {
      /* write the result based on the operation requested. */
      wbuf[po->sfi.channels - 1][i] = (
        op == SP_MATHOP_ADD ? rbuf[c1 - 1][i] + rbuf[c2 - 1][i] : \
        op == SP_MATHOP_SUB ? rbuf[c1 - 1][i] - rbuf[c2 - 1][i] : \
        op == SP_MATHOP_MUL ? rbuf[c1 - 1][i] * rbuf[c2 - 1][i] : \
        op == SP_MATHOP_DIV ? rbuf[c1 - 1][i] / rbuf[c2 - 1][i] : 0.0);
    }
  } while (pipes_write (po, wbuf, frames) == SP_OK);

  /* free the input and output pipes buffer memories. */
  pipes_buf_free (pi, frames, rbuf);
  pipes_buf_free (po, frames, wbuf);

  /* attempt to close the input pipe. */
  if (pipes_close (pi) != SP_OK) {
    /* print a failure message and exit the application. */
    failf ("unable to close input pipe");
  }

  /* attempt to close the output pipe. */
  if (pipes_close (po) != SP_OK) {
    /* print a failure message and exit the application. */
    failf ("unable to close output pipe");
  }
}

